#include <WiFi.h>
#include <WebServer.h>
#include <SPIFFS.h> //闪存文件系统
#include <PubSubClient.h>
#include <HTTPClient.h>
// #define MQTT_MAX_PACKET_SIZE 512
// #define MQTT_KEEPALIVE 300
#include <ArduinoJson.h>
#include "DHT.h"
#include "SHA256.h"

#define LED2 2
WebServer server(80); //创建Web服务端口为80
void wificonfig();
void removeConfig();
// WiFiClient client;
void printLocalTime();
const char *ntpServer = "pool.ntp.org";
const long gmtOffset_sec = 8 * 3600;
const int daylightOffset_sec = 0;

/* 设备的三元组信息*/
#define PRODUCT_KEY "a1MlOAISpMn"
#define DEVICE_NAME "SN0001"
#define DEVICE_SECRET "fcdf9b11c61c89a7bbeb6dbde951d772"
// {
//   "ProductKey": "a1MlOAISpMn",
//   "DeviceName": "SN0001",
//   "DeviceSecret": "fcdf9b11c61c89a7bbeb6dbde951d772"
// }
/* 线上环境域名和端口号，不需要改 */
#define REGION_ID "cn-shanghai"
#define MQTT_SERVER PRODUCT_KEY ".iot-as-mqtt." REGION_ID ".aliyuncs.com"
#define MQTT_PORT 1883
#define MQTT_USRNAME DEVICE_NAME "&" PRODUCT_KEY
#define CLIENT_ID "ESP32SN0001"
#define CONTENT_STR_FORMAT "clientId" CLIENT_ID "deviceName" DEVICE_NAME "productKey" PRODUCT_KEY "timestamp%d"
#define ALINK_BODY_FORMAT "{\"id\":\""  CLIENT_ID  "\",\"version\":\"1.0\",\"method\":\"thing.event.property.post\",\"params\":%s}"
#define ALINK_TOPIC_PROP_POST "/sys/" PRODUCT_KEY "/" DEVICE_NAME "/thing/event/property/post"

WiFiClient espClient;
PubSubClient client(espClient);

String client_id;
String str1;
char MQTT_PASSWD[64];

static void hex_to_str(char *ptr, char *buf, int len);
void mqttCheckConnect();
void callback(char *topic, byte *payload, unsigned int length);

void setup()
{
    Serial.begin(115200);
    pinMode(0, INPUT_PULLUP);   //板子上的FLASH按键,
    pinMode(LED2, OUTPUT);
    digitalWrite(LED2, LOW);
    delay(1000);
    if (digitalRead(0) == 0)
    { //按键扫描
        delay(5000);
        if (digitalRead(0) == 0)
        {
            removeConfig();
        }
    }
    digitalWrite(LED2, LOW);
    Serial.println();
    wificonfig();
    configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);

    delay(100);
    char hashs[32];
    char buff[128];
    int timestamp = millis();
    sprintf(buff, CONTENT_STR_FORMAT, timestamp);
    client_id = String(CLIENT_ID) + "|securemode=3,signmethod=hmacsha256,timestamp=" + String(timestamp) + '|';
    // Serial.println(client_id);
    // Serial.println(CONTENT_STR_FORMAT);
    // Serial.println(ALINK_BODY_FORMAT);
    
    SHA256 sha256;
    sha256.resetHMAC(DEVICE_SECRET, strlen(DEVICE_SECRET));
    sha256.update(buff, strlen(buff));
    sha256.finalizeHMAC(DEVICE_SECRET, strlen(DEVICE_SECRET), hashs, 32);
    hex_to_str(MQTT_PASSWD, hashs, 32);

    delay(50);

    mqttCheckConnect();
}
void loop()
{
    char param[128];
    char jsonbuf[256];
    float dt1, dt2, dh, dco2;
    dt1 = random(1000) / 10.0;
    dt2 = random(1000) / 10.0;
    dh = random(1000) / 10.0;
    dco2 = random(1000) / 10.0;

    Serial.print("dt1:");
    Serial.println(dt1);
    Serial.print("dt2:");
    Serial.println(dt2);
    Serial.print("dh:");
    Serial.println(dh);
    Serial.print("co2:");
    Serial.println(dco2);
    
    sprintf(param, "{\"temperature_d\":%.1f,\"temperature_r\":%.1f,\"Humidity\":%.1f,\"co2\":%.1f}", dt1, dt2, dh, dco2);
    sprintf(jsonbuf, ALINK_BODY_FORMAT, param);
    Serial.println(jsonbuf);
    boolean d = client.publish(ALINK_TOPIC_PROP_POST, jsonbuf);
    if (d)
    {
        Serial.println("publish T and H success");
        client.loop();
    }
    else
    {
        Serial.println("publish T and H fail");
    }
    printLocalTime();
    delay(2000);
}

void printLocalTime()
{
    struct tm timeinfo;
    if (!getLocalTime(&timeinfo))
    {
        Serial.println("Failed to obtain time");
        return;
    }
    Serial.print("Now Time :");
    Serial.println(&timeinfo, "%A, %B %d %Y %H:%M:%S");
    // Serial.println("###");
}

void handleRoot()
{ //展示网页的关键代码
    // Serial.println("handleroot");
    if (SPIFFS.exists("/index.html"))
    {

        // Serial.println("exists");
        File index = SPIFFS.open("/index.html", "r");
        server.streamFile(index, "text/html");
        index.close();
    }
}

void handleConnect()
{                                     //处理配置信息的函数
    String ssid = server.arg("ssid"); // arg是获取请求参数，视频中最后面展示了请求的完整链接
    String password = server.arg("password");
    server.send(200, "text/plain", "OK");
    WiFi.mode(WIFI_STA);                        //改变wifi模式
    WiFi.begin(ssid.c_str(), password.c_str()); // String类型直接用会报错，不要问为什么，转成char *就行了。
    while (WiFi.status() != WL_CONNECTED)
    {
        delay(500);
        Serial.println(".");
    }
    Serial.println(WiFi.localIP());
    removeConfig(); // 不管有没有配置先删除一次再说。
    String payload; // 拼接构造一段字符串形式的json数据长{"ssid":"xxxxx","password":"xxxxxxxxxxx"}
    payload += "{\"ssid\":\"";
    payload += ssid;
    payload += "\",\"password\":\"";
    payload += password;
    payload += "\"}";
    File wifiConfig = SPIFFS.open("/config.json", "w");
    wifiConfig.println(payload); //将数据写入config.json文件中
    wifiConfig.close();
}

void removeConfig()
{
    if (SPIFFS.exists("/config.json"))
    { // 判断有没有config.json这个文件
        if (SPIFFS.remove("/config.json"))
        {
            Serial.println("删除旧配置");
        }
        else
        {
            Serial.println("删除旧配置失败");
        }
    }
}
void wificonfig()
{
    if (SPIFFS.begin())
    { // 打开闪存文件系统，记得在你连接板子下载的过程中选Flash Size的时候不要选no SPIFFS,你可以选1M、2M、3M都无所谓，因为两个文件都很小
        Serial.println("闪存文件系统打开成功");
        //    SPIFFS.format();
    }
    else
    {
        Serial.println("闪存文件系统打开失败");
    }
    if (SPIFFS.exists("/config.json"))
    { // 判断有没有config.json这个文件
        Serial.println("存在配置信息,正在自动连接");
        const size_t capacity = JSON_OBJECT_SIZE(1) + JSON_OBJECT_SIZE(2) + 60; //分配一个内存空间
        DynamicJsonDocument doc(capacity);                                      // 声明json处理对象
        File configJson = SPIFFS.open("/config.json", "r");
        deserializeJson(doc, configJson); // json数据序列化
        const char *ssid = doc["ssid"];
        const char *password = doc["password"];
        WiFi.mode(WIFI_STA);        // 更换wifi模式
        WiFi.setSleep(false);       //关闭STA模式下wifi休眠，提高响应速度
        WiFi.begin(ssid, password); // 连接wifi
        while (WiFi.status() != WL_CONNECTED)
        {
            delay(500);
            Serial.println(".");
        }
        configJson.close();
        Serial.print("localIP:");
        Serial.println(WiFi.localIP());
    }
    else
    {
        Serial.println("不存在配置信息,正在打开web配网模式");
        IPAddress softLocal(192, 168, 1, 1);
        IPAddress softGateway(192, 168, 1, 1);
        IPAddress softSubnet(255, 255, 255, 0);
        WiFi.softAPConfig(softLocal, softGateway, softSubnet);
        WiFi.softAP("esp32", "12345678"); //这里是配网模式下热点的名字和密码
        Serial.println(WiFi.softAPIP());
    }
    server.on("/", handleRoot);       // web首页监听
    server.on("/set", handleConnect); // 配置ssid密码监听
    server.begin();
    while (WiFi.status() != WL_CONNECTED)
    {
        server.handleClient();
    }
    server.close();
}

void mqttCheckConnect()
{
    client.setServer(MQTT_SERVER, MQTT_PORT); /* 连接WiFi之后，连接MQTT服务器 */
    client.setCallback(callback);
    while (!client.connected())
    {
        Serial.println("Connecting to MQTT Server ...");
        Serial.println(client_id.c_str());
        if (client.connect(client_id.c_str(), MQTT_USRNAME, MQTT_PASSWD))
        {
            Serial.println("MQTT Connected!");
        }
        else
        {
            Serial.println("ERR INFO:");
            Serial.println(client_id);
            Serial.println(MQTT_USRNAME);
            Serial.println(MQTT_PASSWD);
            Serial.print("MQTT Connect err:");
            Serial.println(client.state());
            delay(5000);
        }
    }
}

static void hex_to_str(char *ptr, char *buf, int len)
{
    for (int i = 0; i < len; i++)
    {
        sprintf(ptr, "%02x", buf[i]);
        ptr += 2;
    }
}

void callback(char *topic, byte *payload, unsigned int length)
{
    Serial.print("Message arrived [");
    Serial.print(topic);
    Serial.print("] ");
    
    payload[length] = '\0';
    Serial.println((char *)payload);
}